/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2011, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/**
 *  \file
 *  Implement simple DBGU usage as stream receiver.
 */

/*-------------------------------
 *          Headers
 *-------------------------------*/

//#include <board.h>
#include <stdio.h>
#include "dbg_util.h"
#include "timer.h"

#include "misc/console.h"

/*-------------------------------
 *          Defines
 *-------------------------------*/

/** Data RX timeout in binary start up */
#define TIMEOUT_RX_START        (1000*20)
/** Data RX timeout default value */
#define TIMEOUT_RX              (200)

/* ASCII Character Codes */
#define SOH             0x01
#define STX             0x02
#define EOT             0x04
#define CTRL_D          0x04 /**< Transfer Done */
#define ACK             0x06
#define NAK             0x15
#define CAN             0x18 /**< Cancel transfer */
#define CTRL_X          0x24

/* 1K XMODEM Parameters */
#define SOH_LENGTH      128
#define STX_LENGTH      1024
#define SOH_TIMEOUT     1000

/*-------------------------------
 *          Local functions
 *-------------------------------*/

/**
 * \brief Compute the CRC
 */
static uint16_t
_GetCRC(uint8_t bByte, uint16_t wCrc)
{
	int32_t cnt;
	uint8_t newBit;
	for (cnt = 7; cnt >= 0; cnt--) {
		newBit = ((wCrc >> 15) & 0x1) ^ ((bByte >> cnt) & 0x1);
		wCrc <<= 1;
		if (newBit)
			wCrc ^= (0x1021);
	}
	return wCrc;

}

/*-------------------------------
 *          Exported functions
 *-------------------------------*/

/**
 * \brief Receives byte with timeout.
 * \param pByte pointer to locate received byte, can be NULL
 *              to discard data.
 * \param timeOut timeout setting, in number of ticks.
 */
uint8_t
DbgReceiveByte(uint8_t * pByte, uint32_t timeOut)
{
	uint32_t tick;
	uint32_t delay;
	tick = timer_get_tick();
	while (1) {
		if (console_is_rx_ready()) {
			uint8_t tmp = console_get_char();
			if (pByte)
				*pByte = tmp;
			return 1;
		}

		if (timeOut == 0) {
			/* Never timeout */
		} else {
			delay = timer_get_interval(tick, timer_get_tick());
			if (delay > timeOut) {
				return 0;
			}
		}
	}
}

/**
 * \brief Receives raw binary file through DBGU.
 * \param bStart 1 to start a new data stream.
 * \param address receiving data address
 * \param maxSize max receive data size in bytes
 * \return number of received bytes
 */
uint32_t
DbgReceiveBinary(uint8_t bStart, uint32_t address, uint32_t maxSize)
{
	volatile uint32_t tick0;
	uint32_t delay;
	uint8_t *pBuffer = (uint8_t *) address;
	uint8_t xSign = 0;
	uint32_t rxCnt = 0;

	if (maxSize == 0)
		return 0;

	if (bStart) {
		printf("\n\r-- Please start binary data in %d seconds:\n\r",
		       TIMEOUT_RX_START / 1000);
		tick0 = timer_get_tick();
		while (1) {
			if (console_is_rx_ready()) {
				pBuffer[rxCnt++] = console_get_char();
				console_put_char(' ');
				break;
			} else {
				delay = timer_get_interval(tick0, timer_get_tick());
				if ((delay % 1000) == 0) {
					if (xSign == 0) {
						console_put_char('*');
						xSign = 1;
					}
				} else if (xSign) {
					xSign = 0;
				}

				if (delay > TIMEOUT_RX_START) {
					printf("\n\rRX timeout!\n\r");
					return rxCnt;
				}
			}
		}
	}
	/* Get data */
	while (1) {
		tick0 = timer_get_tick();
		while (1) {
			if (console_is_rx_ready()) {
				pBuffer[rxCnt++] = console_get_char();
				if ((rxCnt % (10 * 1024)) == 0) {
					console_put_char('.');
				}
				if (rxCnt >= maxSize) {
					/* Wait until file transfer finished */
					return rxCnt;
				}
				break;
			}
			delay = timer_get_interval(tick0, timer_get_tick());
			if (delay > TIMEOUT_RX) {
				return rxCnt;
			}
		}
	}
}

/**
 * \brief Receives raw binary file through DBGU.
 *
 * \note When "CCC..", uses Ctrl + D to exit.
 *
 * \param pktBuffer 1K size packet buffer
 * \param address   receiving data address
 * \param maxSize   max receive data size in bytes
 * \return number of received bytes
 */
uint32_t
DbgReceive1KXModem(uint8_t * pktBuffer, uint32_t address, uint32_t maxSize)
{
	uint8_t inChar;
	uint32_t i, index = 0, pktLen = 0;
	uint8_t pktNum = 0, prevPktNum = 0;
	uint32_t error = 0;
	uint16_t inCrc, myCrc;
	uint8_t inCheckSum = 0xFF, checkSum = 0;
	uint8_t *pBuffer = (uint8_t *) address;
	uint32_t totalLen = 0;

	console_put_char('C');
	while (1) {
		if (!DbgReceiveByte(&inChar, SOH_TIMEOUT)) {
			console_put_char('C');
			continue;
		}
		/* Done */
		if (EOT == inChar) {
			error = 0;
			console_put_char(ACK);
			break;
		} else if (CAN == inChar) {
			error = 2;
		} else if (CTRL_X == inChar) {
			error = 3;
		} else if (SOH == inChar) {
			pktLen = SOH_LENGTH;
		} else if (STX == inChar) {
			pktLen = STX_LENGTH;
		} else
			continue;
		/* Get Packet Number */
		if (!DbgReceiveByte(&pktNum, SOH_TIMEOUT))
			error = 4;
		/* Get 1's complement of packet number */
		if (!DbgReceiveByte(&inChar, SOH_TIMEOUT))
			error = 5;
		/* Get 1 packet of information. */
		checkSum = 0;
		myCrc = 0;
		index = 0;
		for (i = 0; i < pktLen; i++) {
			if (!DbgReceiveByte(&inChar, SOH_TIMEOUT))
				error = 6;
			checkSum += inChar;
			myCrc = _GetCRC(inChar, myCrc);
			if (pktNum != prevPktNum) {
				pktBuffer[index++] = inChar;
			}
		}
		/* Get CRC bytes */
		if (!DbgReceiveByte(&inCheckSum, SOH_TIMEOUT))
			error = 7;
		inCrc = inCheckSum << 8;
		if (!DbgReceiveByte(&inCheckSum, SOH_TIMEOUT))
			error = 7;
		inCrc += inCheckSum;
		/* If CRC error, NAK */
		if (error || (inCrc != myCrc)) {
			console_put_char(NAK);
			error = 0;
		}
		/* Save packet, ACK and next */
		else {
			prevPktNum = pktNum;

			/* Buffer full? */
			if (totalLen + pktLen > maxSize) {
				/* Copy until buffer full? */
				/* Stop transfer */
				console_put_char(CAN);
				return totalLen;
			}

			/* Copy the packet */
			for (i = 0; i < pktLen; i++) {
				pBuffer[totalLen + i] = pktBuffer[i];
			}
			totalLen += pktLen;
			console_put_char(ACK);
		}
	}

	return totalLen;
}
